package com.youngjun.common.security.access;

import com.youngjun.common.security.constant.SecurityConfigConstant;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;


public class CustomAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        int configAttributesSize = configAttributes.size();
        //没有配置需要的权限?
        if (configAttributesSize == 0) {
            throw new AccessDeniedException("Access is denied");
        }
        List<String> authorities = configAttributes
                .stream()
                .map(ConfigAttribute::getAttribute)
                .collect(Collectors.toList());
        //denyAll
        if (authorities.contains(SecurityConfigConstant.DENY_ALL)) {
            throw new AccessDeniedException("Access is denied");
        }

        //permitAll
        if (authorities.contains(SecurityConfigConstant.PERMIT_ALL)) {
            //configAttributes size is not 1
            if (configAttributesSize != 1) {
                throw new AccessDeniedException("Access is denied");
            } else {
                return;
            }
        }

        //authenticated, but configAttributes size is not 1 or the user is anonymous
        if (authorities.contains(SecurityConfigConstant.AUTHENTICATED)) {
            if (configAttributesSize == 1 && !(authentication instanceof AnonymousAuthenticationToken)) {
                return;
            } else {
                throw new AccessDeniedException("Access is denied");
            }
        }

        //authentication not contains any of the configAttributes
        if (authentication
                .getAuthorities()
                .stream()
                .anyMatch(
                        (Predicate<GrantedAuthority>) grantedAuthority -> authorities.contains(grantedAuthority.getAuthority())
                )) {
            return;
        }

        //default access is denied;
        throw new AccessDeniedException("Access is denied");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}
